const nativeWatch = {}.watch;

class Vue {
    constructor(options) {
        // vm就是this
        this.$options = options
        // console.log(this.$options);
        // console.log(this);
        this.$watchEvent = {}
        // if (typeof options.beforeCreate == 'function') {
        //     options.beforeCreate.call(this)
        // }
        this.$data = options.data

        this.proxyData()
        this.observe()
        // if (typeof options.created == 'function') {
        //     options.created.call(this)
        // }
        // if (typeof options.beforeMount == 'function') {
        //     options.beforeMount.call(this)
        // }
        this.$el = document.querySelector(options.el)

        // if (typeof options.mounted == 'function') {
        //     options.mounted.call(this)
        // }
        this.compile(this.$el)
        // computed
        if (this.$options.computed) {
            // console.log('有computed');
            this.initComputed(this, this.$options.computed);
        }
        // watch
        // if (this.$options.watch && this.$options.watch !== nativeWatch) {
        //     // initWatch(vm, this.$options.watch);
        // }
    }
    // 双向绑定 数据劫持
    proxyData() {
        let that = this
        for (const key in this.$data) {
            Object.defineProperty(this, key, {
                get() {
                    return this.$data[key];
                },
                set(val) {
                    this.$data[key] = val
                }
            })

        }
    }

    // 观察更新
    observe(obj) {
        for (const key in this.$data) {
            let value = this.$data[key]
            let that = this
            Object.defineProperty(this.$data, key, {
                get() {
                    return value
                },
                set(val) {
                    value = val
                    if (that.$watchEvent[key]) {
                        that.$watchEvent[key].forEach((item, index) => {
                            item.update()
                        })
                    }
                }
            })
        }
    };

    // 模板解析 将属性名转为数据 将事件绑定到元素
    compile(node) {
        node.childNodes.forEach((item, index) => {
            if (item.nodeType == 1) {
                // 元素节点
                if (item.hasAttribute('@click')) {
                    let vmKey = item.getAttribute('@click').trim()
                    item.addEventListener('click', (event) => {
                        this.eventFn = this.$options.methods[vmKey].bind(this)
                        this.eventFn(event)
                    })
                }
                if (item.hasAttribute('v-model')) {
                    let vmKey = item.getAttribute('v-model').trim()
                    if (this.hasOwnProperty(vmKey)) {
                        item.value = this[vmKey]
                    }
                    item.addEventListener('input', (event) => {
                        this[vmKey] = item.value
                    })
                }
                if (item.childNodes.length > 0) {
                    this.compile(item)
                }

            } else if (item.nodeType == 3) {
                // 文本节点
                let reg = /\{\{(.*?)\}\}/g
                let text = item.textContent
                let that = this
                const keys = [];
                text.replace(reg, (match, $1) => {
                    keys.push($1.trim());
                    return match;
                });

                function getObjData(arr, data, item, vmKey) {
                    let firstKey = arr.shift()
                    firstKey = firstKey.trim()
                    let node = {}
                    node.value = data[firstKey]

                    if (data.hasOwnProperty(firstKey)) {
                        let watch = new watcher(that, firstKey, item, 'textContent', vmKey)
                        if (that.$watchEvent[firstKey]) {
                            that.$watchEvent[firstKey].push(watch)
                        } else {
                            that.$watchEvent[firstKey] = []
                            that.$watchEvent[firstKey].push(watch)
                        }
                    }
                    let res = data[firstKey]
                    if (arr.length !== 0) {
                        return getObjData(arr, res, item)
                    } else {
                        return JSON.stringify(res).replace(/^['"]|['"]$/g, '');
                    }
                }
                // 给节点赋值
                item.textContent = text.replace(reg, (match, vmKey) => {
                    vmKey = vmKey.trim()
                    if (this.hasOwnProperty(vmKey)) {
                        let watch = new watcher(this, vmKey, item, 'textContent', vmKey)

                        if (this.$watchEvent[vmKey]) {
                            this.$watchEvent[vmKey].push(watch)
                        } else {
                            this.$watchEvent[vmKey] = []
                            this.$watchEvent[vmKey].push(watch)
                        }
                    }

                    let index = vmKey.indexOf('.')
                    if (index !== -1) {
                        let objArr = vmKey.split('.')
                        return getObjData(objArr, this.$data, item, vmKey)
                    } else {
                        // JSON.stringify将[object object] 展开显示 {"dog":{"name":"小狗"}}
                        let res = JSON.stringify(this.$data[vmKey])
                        return res?.replace(/^['"]|['"]$/g, '');
                    }

                })
            }
        });
    }

    initComputed(vm, computed) {
        console.log('标记');
        
        const computedWatcherOptions = { lazy: true };
        // $flow-disable-line
        const watchers = (vm._computedWatchers = Object.create(null));
        // computed properties are just getters during SSR
        const isSSR = isServerRendering();
        for (const key in computed) {
            const userDef = computed[key];
            const getter = isFunction(userDef) ? userDef : userDef.get;
            if (getter == null) {
                warn$2(`Getter is missing for computed property "${key}".`, vm);
            }
            if (!isSSR) {
                // create internal watcher for the computed property.
                watchers[key] = new Watcher(vm, getter || noop, noop, computedWatcherOptions);
            }
            // component-defined computed properties are already defined on the
            // component prototype. We only need to define computed properties defined
            // at instantiation here.
            if (!(key in vm)) {
                defineComputed(vm, key, userDef);
            }
            else {
                if (key in vm.$data) {
                    warn$2(`The computed property "${key}" is already defined in data.`, vm);
                }
                else if (vm.$options.props && key in vm.$options.props) {
                    warn$2(`The computed property "${key}" is already defined as a prop.`, vm);
                }
                else if (vm.$options.methods && key in vm.$options.methods) {
                    warn$2(`The computed property "${key}" is already defined as a method.`, vm);
                }
            }
        }
    }



}

// 观察者类，用于关联DOM元素和对应的数据，实现更新逻辑
class watcher {
    // vm对象  key属性名称 node节点 attr改变文本节点内容的字符串
    constructor(vm, key, node, attr, vmKey) {
        this.vm = vm;
        this.key = key;
        this.node = node;
        this.attr = attr;
        this.vmKey = vmKey;
    }
    // 执行改变操作
    update() {
        function getObjData(arr, data) {
            let firstKey = arr.shift()
            firstKey = firstKey.trim()
            let res = data[firstKey]
            if (arr.length !== 0) {
                return getObjData(arr, res)

            } else {
                return res
            }
        }

        let index = this.vmKey.indexOf('.')
        let res
        if (index !== -1) {
            let objArr = this.vmKey.split('.')
            res = JSON.stringify(getObjData(objArr, this.vm))
            this.node[this.attr] = res.replace(/^['"]|['"]$/g, '');
        } else {
            res = JSON.stringify(this.vm[this.key])
            this.node[this.attr] = res.replace(/^['"]|['"]$/g, '');
        }
    }
}

